iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 18
0
Software Development

Android Architecture系列 第 18

Test part 2:Retrofit api calls and common util

  • 分享至 

  • xImage
  •  

今天開始會講個幾天的Test,回顧一下App架構圖:

               Guide to App Architecture

接下來會把各區塊的Test都寫一下,從底層開始往上寫。Room已經在昨天完成了,今天會寫Retrofit的測試,並對之後測試做一點前置準備。

Testing Retrofit

API的測試通常會用mock的方式模擬出server和連線,除了避免對正式機發出連線以外,也可以自行編寫response內容以便驗證。另外我們的Retrofit使用了LiveDataCallAdapter,連線回傳結果會是LiveData,在測試上也會稍有不同。

1.加入dependencies
我們使用MockWebServer來模擬api連線:

testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'

加入Architecture Component的testing library,因為測試時我們希望LiveData是同步(synchronously)執行,否則會在收到結果前就驗證而導致失敗,此testing library有提供@Rule讓LiveData同步執行。

testImplementation "android.arch.core:core-testing:1.0.0"

2.建立response內容
將API會回傳的內容存成檔案以供MockWebServer讀取。我們要測試的是GitHub API的search repositories連線,以我們要的搜尋關鍵字呼叫該連線並將response內容存下來,例如https://api.github.com/search/repositories?q=yigit ,將結果儲存為search.json,放在src > test > resources > api-response路徑下。

3.testing
測試連線不用在Android裝置上執行,因此我們在test路徑下建立test case。

關鍵的地方是加入@Rule讓LiveData同步執行。

@Rule
public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();

MockWebServer直接用new建立,並將Retrofit的baseUrl改成mockWebServer.url("/")就完成模擬server了,記得在@After中關閉。


private GithubService githubService;

private MockWebServer mockWebServer;

@Before
public void createService() throws IOException {
    mockWebServer = new MockWebServer();
    githubService = new Retrofit.Builder()
                .baseUrl(mockWebServer.url("/"))
                ...
                .create(GithubService.class);
}

@After
public void stopService() throws IOException {
    mockWebServer.shutdown();
}

完整程式:

@RunWith(JUnit4.class)
public class GithubServiceTest {
    @Rule
    public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();

    private GithubService githubService;

    private MockWebServer mockWebServer;

    @Before
    public void createService() throws IOException {
        mockWebServer = new MockWebServer();
        githubService = new Retrofit.Builder()
                .baseUrl(mockWebServer.url("/"))
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(new LiveDataCallAdapterFactory())
                .build()
                .create(GithubService.class);
    }

    @After
    public void stopService() throws IOException {
        mockWebServer.shutdown();
    }

    @Test
    public void search() throws IOException, InterruptedException {
        enqueueResponse("search.json");
        ApiResponse<RepoSearchResponse> response = getValue(
                githubService.searchRepos("foo"));

        assertThat(response, notNullValue());
        assertThat(response.body.getTotal(), is(41));
        assertThat(response.body.getItems().size(), is(30));
    }

    private void enqueueResponse(String fileName) throws IOException {
        InputStream inputStream = getClass().getClassLoader()
                .getResourceAsStream("api-response/" + fileName);
        BufferedSource source = Okio.buffer(Okio.source(inputStream));
        MockResponse mockResponse = new MockResponse();
        mockWebServer.enqueue(mockResponse
                .setBody(source.readString(StandardCharsets.UTF_8)));
    }
}

enqueueResponse(String fileName)中我們讀取search.json,接著mockWebServer會用enqueue將json內容存起來,這樣下一個call的response就會是此內容。

githubService.searchRepos("foo")為發出的call,得到的response即為存於mockWebServer的search.json內容,接著我們就可以驗證response的結果與json文件是否一致。

其中有一個問題是getValue(),這是昨天建好的LiveDataTestUtil的method,但昨天我們是建立在androidTest中,今天則是在test下作業所以會抓不到getValue()

對於這些共用的Util,我們建一個新的test-common路徑存放,將來測試才方便。

Common Test Util

由於test case會依是否需在Android裝置上執行而分別放在androidTest和test兩個路徑,但有時會有共用的功能,例如剛剛提到的LiveDataTestUtil或是建立新的Repo這種到處都用到的method,我們就可以建立新的test-common路徑來存放。

於src資料夾當中建立test-common資料夾,後續都跟test一樣,例如src\test-common\java\ivankuo\com\itbon2018\

接著必須在build.gradle中為androidTest和test加上該路徑,這樣它們兩個才能讀到新路徑中的程式,如果沒加這段的話會Android Studio不會引用新的路徑,不能使用新路徑中的程式且在package那邊也看不到:

android {
    ...

    sourceSets {
        ...
        androidTest.java.srcDirs += "src/test-common/java"
        test.java.srcDirs += "src/test-common/java"
    }
}

接著就可以在test-common中加入Util了,例如TestUtil讓我們建立Repo時方便一點:

public class TestUtil {

    public static Repo createRepo(String owner, String name, String description) {
        return createRepo(1, owner, name, description);
    }

    public static Repo createRepo(int id, String owner, String name, String description) {
        return new Repo(id, name, owner + "/" + name,
                description, new Owner(owner, null, null), 3);
    }
}

用Project檢視方式看package會長這樣:
https://ithelp.ithome.com.tw/upload/images/20180107/20103849q413laXOMQ.png
第一層依序有androidTest、main、test、test-common,其中test裡有resources資料夾存放json文件。

用Android檢視方式就會好看一點了:
https://ithelp.ithome.com.tw/upload/images/20180107/201038494iW1nEP64J.png


GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day18-test-retrofit


上一篇
Test part 1:Room DAO and migration
下一篇
Test part 3:Repository
系列文
Android Architecture30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言